package utility

import (
	"context"
	"github.com/gogf/gf/v2/container/gmap"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/frame/gins"
	"github.com/gogf/gf/v2/os/grpool"
	"github.com/gogf/gf/v2/util/gconv"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/mongo/readpref"
)


/*
by:axin
并发锁写内存
*/
var mg = gmap.NewStrAnyMap(true)

//连接池
type mongoConnectPool struct {
	instances         []*mongo.Client
	index int
	poolNum          int
	options          *options.ClientOptions
	database         string
}

/*
by:axin
初始化连接
*/
func MgConn (key string) *mongoConnectPool {

	config := gins.Config()

//	config.GetAdapter().(*gcfg.AdapterFile).SetFileName("mongodb.yaml")

	get, err := config.Get(context.TODO(), "mongodb")

	cfg := get.Map()

	if err != nil {
		g.Log().Error(context.TODO(), "获取mongo配置失败", err)
	}
	//gutil.MapPossibleItemByKey(get.Map(), "mongodb")

	conn := mg.GetOrSetFuncLock(key, func () interface{} {

		if len(cfg) > 0 {
			//读取到配置进行连接
			if v, ok := cfg[key]; ok {
				opts := gconv.Map(v)
				var mongoConPool = new(mongoConnectPool)
				mongoConPool.options = options.Client().ApplyURI(gconv.String(opts["uri"]))
				mongoConPool.poolNum = gconv.Int(opts["poolNum"])
				mongoConPool.database = gconv.String(opts["database"])
				mongoConPool.mongoConnect()

				return mongoConPool
			} else {
				g.Log().Error(context.TODO(), "mongodb配置不正确")
			}
		}

		return nil
	})


	return conn.(*mongoConnectPool)
}


/*
by:axin
清楚连接池
*/
func (mgPool *mongoConnectPool) MongoClear() {
	if mgPool != nil {
		mgPool.MongoDisconnect()
		mgPool = nil
	}
}

func (mgPool *mongoConnectPool) MongoDisconnect() {
	if mgPool.instances == nil || len(mgPool.instances) <= 0 {
		return
	}
	for _, instance := range mgPool.instances {
		_ = instance.Disconnect(context.TODO())
	}
	mgPool.instances = []*mongo.Client{}
}


/*
by:axin
创建连接
*/
func (mgPool *mongoConnectPool) mongoConnect() {
	//将 poolNum 默认值设置为 10
	if mgPool.poolNum == 0 {
		mgPool.poolNum = 10
	}

	for i := 0; i < mgPool.poolNum; i++ {
		instance, err := mgPool.getClient()
		if err != nil {
			continue
		}
		mgPool.instances = append(mgPool.instances, instance)
	}
}


/*
by:axin
获取连接
*/
func (mgPool *mongoConnectPool) getClient() (*mongo.Client, error) {
	client, err := mongo.Connect(context.TODO(), mgPool.options)
	if err != nil {
		g.Log().Error(context.TODO(), "mongodb连接错误：", err)
		return nil, err
	}
	return client, nil
}


/*
by:axin
获取实例
*/
func (mgPool *mongoConnectPool) GetInstance() *mongo.Client {
	var instance *mongo.Client
	instance = mgPool.instances[mgPool.index]

	//判断连接
	/*	readpref.Primary() 是 MongoDB Go 驱动（go.mongodb.org/mongo-driver/mongo）中的一部分。它用于指定在 MongoDB 集群中进行读操作时的主要节点（Primary）。

		在 MongoDB 中，数据分布在一个集群中的多个节点上，其中一个节点被指定为主要节点（Primary），其余的是次要节点（Secondary）。主要节点是默认用于读取和写入操作的节点，而次要节点则用于读操作（在读偏好设置为 Secondary 或 PrimaryPreferred 时）。*/
	if err := instance.Ping(context.TODO(), readpref.Primary()); err != nil {
		_ = grpool.Add(context.TODO(), func (ctx context.Context) {
			_ = instance.Disconnect(context.TODO())
			instance, err = mongo.Connect(context.TODO(), mgPool.options)
			if err != nil {
				g.Log().Error(context.TODO(), err)
			} else {
				mgPool.instances[mgPool.index] = instance
			}
		})
		mgPool.index = (mgPool.index + 1) % len(mgPool.instances)
		return mgPool.GetInstance()
	}

	mgPool.index = (mgPool.index + 1) % len(mgPool.instances)
	return instance
}



/*
by:axin
Ping连接，连接前调用
*/
func (mgPool *mongoConnectPool) Ping() error {
	var instance *mongo.Client
	instance = mgPool.instances[mgPool.index]
	//确定一下连接有没有中断
	if err := instance.Ping(context.TODO(), readpref.Primary()); err != nil {
		return err
	}
	return nil
}

/*
by:axin
获取数据库里面的集合
*/
func (mgPool *mongoConnectPool) GetColl(collection string) *mongo.Collection {
	return mgPool.GetInstance().Database(mgPool.database).Collection(collection)
}





/*
问题：通过GetOrSetFuncLock 存储连接池的方法，和我另外创建一个全局变量代码存储连接池的方法有什么不同

答：使用 GetOrSetFuncLock 存储连接池的方法，相对于直接创建一个全局变量来存储连接池，主要有以下不同之处：

懒加载（Lazy Loading）： GetOrSetFuncLock 的机制是当第一次请求连接池时，才会执行传入的函数来创建连接池并存储在内存中。这样可以在应用程序启动时避免不必要的资源消耗，只有在需要时才创建连接池。

go
Copy code
conn := mg.GetOrSetFuncLock(key, func() interface{} {
    // 创建连接池的逻辑
    return createMongoConnectPool()
})
相比之下，直接使用全局变量会在应用程序启动时立即创建连接池：

var globalMongoConnectPool = createMongoConnectPool()

// 在其他地方使用 globalMongoConnectPool
动态更新： 如果连接池的配置可能会在运行时发生变化，使用 GetOrSetFuncLock 允许你在每次请求时都动态更新连接池。每次请求时都会重新执行传入的函数，允许你在其中进行一些动态的配置检查或更新。

并发安全性： GetOrSetFuncLock 使用了读写锁来确保在并发访问时的安全性。这意味着即使有多个 goroutine 同时请求连接池，也不会出现竞态条件。这对于高并发的应用程序是非常重要的。

相反，直接使用全局变量可能需要额外的加锁机制来保证并发安全性。

总体而言，使用 GetOrSetFuncLock 存储连接池的方法更具有灵活性和懒加载的特性，适用于动态更新和高并发的场景。直接使用全局变量则是一种更简单直接的方法，适用于在应用程序启动时就需要创建和初始化连接池的情况。选择取决于你的具体需求和应用场景。*/











//以下是最初始的测试代码，留着做个对比，可以回忆回忆当时优化的思路
//package service
//
//import (
//	"context"
//	"fmt"
//	"go.mongodb.org/mongo-driver/mongo"
//	"go.mongodb.org/mongo-driver/mongo/options"
//	"gopkg.in/yaml.v3"
//	"io"
//	"log"
//	"os"
//)
//
//
//type myMongo struct {
//
//}
//
//
////mongo配置
//type mongoConfig struct {
//	MongoDB struct {
//		Url      string `yaml:"url"`
//		DataBase string `yaml:"database"`
//	} `yaml:"mongodb"`
//}
//
//func NewMongo() *myMongo {
//	return &myMongo{}
//}
//
//var config *mongoConfig
//
//
//
//func (m *myMongo)connMongoDb(collectionName string) (collection *mongo.Collection) {
//
//	open, readErr := os.Open("./manifest/config/config.yaml")
//
//
//	if readErr != nil {
//		fmt.Println("打开mongodb配置错误")
//	}
//	defer open.Close()
//	configData, readErr := io.ReadAll(open)
//
//	if readErr != nil {
//		fmt.Println("读取mongodb配置错误")
//	}
//
//
//	if readErr := yaml.Unmarshal(configData, &config) ;readErr != nil {
//		fmt.Println("读取mongodb配置错误")
//	}
//
//	// 设置 MongoDB 连接信息
//	clientOptions := options.Client().ApplyURI(config.MongoDB.Url)
//	client, err := mongo.Connect(context.TODO(), clientOptions)
//
//	if err != nil {
//		fmt.Println("mongodb连接错误")
//	}
//
//
//	// 检查连接
//	err = client.Ping(context.TODO(), nil)
//	if err != nil {
//		fmt.Println("mongodb连接断开")
//	}
//
//	//fmt.Println("Connected to MongoDB!")
//
//
//	// 获取 MongoDB 数据库和集合
//	database := client.Database(config.MongoDB.DataBase)
//	collection = database.Collection(collectionName)
//
//	return
//
//}
//
//
//func (m *myMongo) close(client *mongo.Client)  {
//	// 关闭连接
//	err := client.Disconnect(context.TODO())
//	if err != nil {
//		log.Fatal(err)
//	}
//	fmt.Println("Connection to MongoDB closed.")
//}
//
//
////自己看的查询示例，没用
//func example(collection *mongo.Collection)  {
//	// 插入文档
//	insertResult, err := collection.InsertOne(context.TODO(), map[string]interface{}{
//		"key":  "value",
//		"key2": "value2",
//	})
//	if err != nil {
//		log.Fatal(err)
//	}
//	fmt.Println("Inserted a single document: ", insertResult.InsertedID)
//
//}
//
//
